/*

 * Copyright (C) 2021 Huawei Device Co., Ltd.

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *     http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */
package com.melon.myapplication.slice;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.*;

/**
 * 水波纹button
 */
public class RippleView extends Button implements Component.DrawTask, Component.TouchEventListener {
    private static final String TAG = RippleView.class.getSimpleName();
    /**
     * 起始点
     */
    private int mDownX;
    private int mDownY;
    private int minRadius = 20;
    /**
     * 最大执行动画数
     */
    private int maxAnimatorNum = 20;
    private final Rect mRect = new Rect();
    private boolean mPressUp = false;
    private Paint mRevealPaint;
    private int mRippleColor = Color.BLACK.getValue();
    private float mAlphaFactor = 0.2f;
    private int mDuration = 150;

    private ClickedListener clickListener;
    private boolean cancleClick = false;
    private int mMaxRadius;
    private boolean downAnimateEnd = true;

    /**
     * 水波纹动画列表
     */
    ArrayList<AnimatorValue> animatorList = new ArrayList<AnimatorValue>(maxAnimatorNum);

    /**
     * 水波纹半径列表
     */
    ArrayList<Float> radiusList = new ArrayList<Float>(maxAnimatorNum);

    /**
     * constructor
     *
     * @param context 上下文
     */
    public RippleView(Context context) {
        this(context, null);
    }

    /**
     * constructor
     *
     * @param context 上下文
     * @param attrSet 属性
     */
    public RippleView(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    /**
     * constructor
     *
     * @param context   上下文
     * @param attrSet   属性
     * @param styleName 样式名
     */
    public RippleView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initView();
        setTouchEventListener(this);
        addDrawTask(this);
        setBindStateChangedListener(new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component component) {
                mRect.set(getLeft(), getTop(), getRight(), getBottom());
                minRadius = Math.min(getHeight(), getWidth()) / 2;
            }

            @Override
            public void onComponentUnboundFromWindow(Component component) {
            }
        });

    }

    @Override
    public void addDrawTask(DrawTask task) {
        super.addDrawTask(task);
        task.onDraw(this, mCanvasForTaskOverContent);
    }

    private void initView() {
        mRevealPaint = new Paint();
        mRevealPaint.setAntiAlias(true);
        mRevealPaint.setColor(new Color(adjustAlpha(mRippleColor, mAlphaFactor)));
    }

    /**
     * 按下动画
     */
    private void startAnimation() {
        if (animatorList.size() >= maxAnimatorNum) {
            AnimatorValue animatorLast = animatorList.get(animatorList.size() - 1);
            Collections.swap(animatorList, 0, animatorList.size() - 1);
            Collections.swap(radiusList, 0, radiusList.size() - 1);
            animatorLast.stop();
            animatorLast.start();
            return;
        }
        final AnimatorValue mRadiusAnimator = new AnimatorValue();
        mRadiusAnimator.setDuration(mDuration);
        mRadiusAnimator.setLoopedCount(0);
        mRadiusAnimator.setCurveType(AnimatorValue.CurveType.DECELERATE);
        mRadiusAnimator.setValueUpdateListener((animatorValue, value) -> {
            float mDrawRadius = minRadius + (mMaxRadius - minRadius) * value;
            int index = animatorList.indexOf(animatorValue);
            radiusList.set(index, mDrawRadius);
            invalidate();
        });
        mRadiusAnimator.setStateChangedListener(animatorState);
        radiusList.add(0, 0.0F);
        animatorList.add(0, mRadiusAnimator);
        mRadiusAnimator.start();
    }

    private final AnimatorValue.StateChangedListener animatorState = new AnimatorValue.StateChangedListener() {
        @Override
        public void onStart(Animator animator) {
            downAnimateEnd = false;
            mRevealPaint.setAlpha(mAlphaFactor);
        }

        @Override
        public void onStop(Animator animator) {
        }

        @Override
        public void onCancel(Animator animator) {
        }

        @Override
        public void onEnd(Animator animator) {
            int index = animatorList.indexOf(animator);
            if (index == 0) {
                downAnimateEnd = true;
            } else {
                radiusList.set(index, 0.0F);
            }
            if (mPressUp) {
                startUpAction();
            }
        }

        @Override
        public void onPause(Animator animator) {
        }

        @Override
        public void onResume(Animator animator) {
        }
    };

    /**
     * 抬起事件
     */
    private void startUpAction() {
        mRevealPaint.setAlpha(0);
        invalidate();
        if (!cancleClick && getClickedListener() != null) {
            getClickedListener().onClick(RippleView.this);
        }
    }

    private void updateDrawData() {
        /*计算最大半径*/
        int radiusLeftTop = (int) Math.sqrt((double) mDownX * mDownX + mDownY * mDownY);
        int radiusRightTop = (int) Math.sqrt((double) (mRect.right - (mDownX + getLeft())) *
                (mRect.right - (mDownX + getLeft())) +
                mDownY * mDownY);
        int radiusLeftBottom = (int) Math.sqrt((double) mDownX * mDownX +
                (mRect.getHeight() - mDownY) * (mRect.getHeight() - mDownY));
        int radiusRightBottom = (int) Math.sqrt((double) (mRect.right - (mDownX + getLeft())) *
                (mRect.right - (mDownX + getLeft())) +
                (mRect.getHeight() - mDownY) * (mRect.getHeight() - mDownY));
        int[] radius = new int[]{radiusLeftTop, radiusRightTop, radiusLeftBottom, radiusRightBottom};
        mMaxRadius = Math.max(getMax(radius), minRadius);
    }

    private int adjustAlpha(int color, float factor) {
        int alpha = Math.round(Color.alpha(color) * factor);
        int red = red(color);
        int green = green(color);
        int blue = blue(color);
        return Color.argb(alpha, red, green, blue);
    }

    @Override
    public void setClickedListener(ClickedListener listener) {
        this.clickListener = listener;
    }

    @Override
    public ClickedListener getClickedListener() {
        return clickListener;
    }


    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        int action = touchEvent.getAction();
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN: {
                startAnimation();
                mPressUp = false;
                mDownX = (int) touchEvent.getPointerPosition(touchEvent.getIndex()).getX();
                mDownY = (int) touchEvent.getPointerPosition(touchEvent.getIndex()).getY();
                updateDrawData();
                cancleClick = false;
                break;
            }
            case TouchEvent.POINT_MOVE:
                int downX = (int) touchEvent.getPointerPosition(touchEvent.getIndex()).getX();
                int downY = (int) touchEvent.getPointerPosition(touchEvent.getIndex()).getY();
                if (!cancleClick && !mRect.isInclude(downX + mRect.left, downY + mRect.top)) {
                    cancleClick = true;
                    mPressUp = true;
                    if (downAnimateEnd) {
                        startUpAction();
                    }
                }
                break;
            case TouchEvent.CANCEL:
            case TouchEvent.PRIMARY_POINT_UP:
                if (!cancleClick) {
                    mPressUp = true;
                    if (downAnimateEnd) {
                        startUpAction();
                    }
                }
                break;
        }
        return !mPressUp;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (radiusList.iterator().hasNext()) {
            float radius = radiusList.iterator().next();
            canvas.drawCircle(mDownX, mDownY, radius, mRevealPaint);
        }
    }


    private int getMax(int[] radius) {
        if (radius.length == 0) {
            return 0;
        }
        int max = radius[0];
        for (int mRadius : radius) {
            if (mRadius > max) {
                max = mRadius;
            }
        }
        return max;
    }

    private int red(int color) {
        return (color >> 16) & 0xFF;
    }

    private int green(int color) {
        return (color >> 8) & 0xFF;
    }

    private int blue(int color) {
        return color & 0xFF;
    }

}
